using System;
using FluentParser.Expressions;

namespace FluentParser.Expressions.Rules.ErrorHandling
{
    /// <summary>
    /// Error handling for a symbol
    /// </summary>
    /// <example><code>
    /// Symbol symbol = Symbol("SomeText");
    /// ErrorSymbolRule error = Errors.SymbolFatal("ErrorMessage");
    /// Symbol symbolOrError = symbol | error;
    /// 
    /// symbol.Parse("SomeText")             --> "SomeText";
    /// symbol.Parse("SomeOtherText")        --> failure;
    /// symbolOrError.Parse("SomeText")      --> "SomeText";
    /// symbolOrError.Parse("SomeOtherText") --> throws "ErrorMessage";
    /// </code></example>
    public class ErrorSymbolRule : Symbol
    {
        public string Message { get; private set; }
        public ParsingExceptionType TypeException { get; private set; }
        public Symbol RecoverStrategy { get; private set; }

        public ErrorSymbolRule(ParsingExceptionType typeExpression, string text, Symbol recoverStrategy)
        {
            RecoverStrategy = recoverStrategy;
            Message = text;
            TypeException = typeExpression;
            TypeOperation = OperationType.ErrorHandling;
        }

        public override ParsingResult<object> Parse(GrammarContext context, int index)
        {
            ParsingException error = new ParsingException(TypeException, Message, index, 0);
            context.Errors.Add(error);

            if (TypeException == ParsingExceptionType.Warning)
                return null;

            if (context.AllowToRecover && RecoverStrategy != null)
            {
                ParsingResult<object> result = RecoverStrategy.Parse(context, index);
                if (result != null)
                    return result;
            }

            throw new Exception(error.Message);
        }

        public override void ParseNode(GrammarContext context, ParsingNodeResult node)
        {
            ParsingException error = new ParsingException(TypeException, Message, node.Index, node.Length);
            context.Errors.Add(error);
            node.Error = error;

            switch (TypeException)
            {
                case ParsingExceptionType.FatalError:
                    throw error;

                case ParsingExceptionType.Error:
                    if (!context.AllowToRecover || RecoverStrategy == null)
                        throw error;

                    ParsingNodeResult recoverNode = node.AddChild(RecoverStrategy, context);
                    if (recoverNode.IsSuccess)
                    {
                        node.IsSuccess = recoverNode.IsSuccess;
                        node.Value = recoverNode.IsSuccess;
                    }
                    else
                        throw error;

                    break;
            }
        }
    }
}